package nl.hva.three2learn.controller;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import nl.hva.three2learn.authentication.UserAuthentication;
import nl.hva.three2learn.dao.CategoryDao;
import nl.hva.three2learn.dao.ChapterDao;
import nl.hva.three2learn.dao.CourseDao;
import nl.hva.three2learn.dao.CoursePeriodDao;
import nl.hva.three2learn.dao.PaymentDao;
import nl.hva.three2learn.dao.PostDao;
import nl.hva.three2learn.dao.TopicDao;
import nl.hva.three2learn.exception.ForbiddenException;
import nl.hva.three2learn.exception.ResourceNotFoundException;
import nl.hva.three2learn.model.AssignmentFileItem;
import nl.hva.three2learn.model.Category;
import nl.hva.three2learn.model.Chapter;
import nl.hva.three2learn.model.Course;
import nl.hva.three2learn.model.CoursePeriod;
import nl.hva.three2learn.model.Payment;
import nl.hva.three2learn.model.Post;
import nl.hva.three2learn.model.Topic;
import nl.hva.three2learn.model.User;
import nl.hva.three2learn.util.Password;
import nl.hva.three2learn.util.PaymentOptionHelper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/course")
public final class CourseController extends BaseController {

	private static final Logger logger = LoggerFactory
			.getLogger(CourseController.class);

	private static final String SESSION_SUBSCRIBE_ERROR_KEY = "subscribe-error";

	@Autowired
	private CourseDao courseDao;

	@Autowired
	private CoursePeriodDao coursePeriodDao;

	@Autowired
	private CategoryDao categoryDao;

	@Autowired
	private ChapterDao chapterDao;

	@Autowired
	private PaymentDao paymentDao;

	@Autowired
	private TopicDao topicDao;

	@Autowired
	private PostDao postDao;

	@Autowired
	private MessageSource messageSource;

	@RequestMapping("/{courseId}")
	public ModelAndView home(UserAuthentication auth, Locale locale,
			@PathVariable("courseId") long courseId) {

		Course course = courseDao.findById(courseId);

		ModelAndView modelAndView = new ModelAndView("course/home");
		modelAndView.addObject("course", course);

		// Same as: <spring:message code="course.title.viewing"/> but cannot
		// put the tag in the attribute of the wrapper tag
		modelAndView.addObject("title",
				messageSource.getMessage("course.title.viewing", null, locale));

		if (isUserLoggedIn()) {

			// Check if the user has subscribed to the course
			List<Payment> payments = paymentDao.findPaymentsFor(auth.getUser(),
					course);

			Date currentTime = GregorianCalendar.getInstance().getTime();

			boolean hasSubscribed = false;

			for (Payment p : payments) {

				if (!p.hasBeenPayed())
					continue;
				
				CoursePeriod start = p.getPeriod();

				// Is the current date between the start and the end date of the
				// "course start"?
				if (currentTime.after(start.getStart())
						&& currentTime.before(start.getEnd())) {
					hasSubscribed = true;
					break;
				}
			}

			modelAndView.addObject("hasSubscribed", hasSubscribed);
		} else {
			modelAndView.addObject("demoChapters", chapterDao.findDemoChaptersByCourse(course));
		}

		return modelAndView;
	}

	@RequestMapping(value = "/subscribe/{courseId}", method = RequestMethod.GET)
	public ModelAndView subscribe(HttpSession session,
			@PathVariable("courseId") long courseId) {

		Course course = courseDao.findById(courseId);

		// Can't subscribe to course if it doesn't exist
		if (course == null)
			throw new ResourceNotFoundException();

		List<CoursePeriod> periods = coursePeriodDao.findByCourse(course);

		ModelAndView modelAndView = new ModelAndView("course/subscribe");
		modelAndView.addObject("course", course);
		modelAndView.addObject("periods", periods);
		modelAndView.addObject("errors",
				session.getAttribute(SESSION_SUBSCRIBE_ERROR_KEY));

		session.removeAttribute(SESSION_SUBSCRIBE_ERROR_KEY);

		return modelAndView;
	}

	@RequestMapping(value = "/subscribe/{courseId}", method = RequestMethod.POST)
	public String doSubscribe(HttpSession session, Locale locale,
			UserAuthentication auth, @PathVariable("courseId") long courseId,
			@RequestParam("course-period") long coursePeriodId,
			@RequestParam("pay-opt") String payOption) {

		if (!isUserLoggedIn())
			return "redirect:/";
		
		Course course = courseDao.findById(courseId);

		String error = null;

		// User is trying to subscribe to a course that doesn't exist
		if (course == null) {
			error = messageSource.getMessage("subscribe.error.no_such_course",
					null, locale);
		}
		// User is trying to subscribe to a course that has been disabled
		else if (course.isDisabled()) {
			error = messageSource.getMessage("subscribe.error.course_disabled",
					null, locale);
		} else {

			// Check if the period selected by the user exists
			CoursePeriod period = coursePeriodDao.findById(coursePeriodId);

			if (period == null) {
				error = messageSource.getMessage("subscribe.error.period_na",
						null, locale);
			} else if (!PaymentOptionHelper.isValidPaymentOption(payOption)) {
				error = messageSource.getMessage(
						"subscribe.error.invalid_pay_opt", null, locale);
			} else {
				
				// The validation went well, create the payment
				// and add the user to the selected course period
				
				Payment payment = new Payment();
				payment.setHasBeenPayed(true);
				payment.setToken("payment_token");
				payment.setPeriod(period);
				payment.setUser(auth.getUser());
				
				period.getUsers().add(auth.getUser());
				coursePeriodDao.save(period);
				
				paymentDao.save(payment);
			}

		}

		// Something went wrong, can't redirect the user
		// to the 3th party just yet
		if (error != null) {
			session.setAttribute(SESSION_SUBSCRIBE_ERROR_KEY, error);
			return "redirect:/subscribe/" + courseId;
		}

		// TODO: Everything went well redirect the user to the 3th party
		return "/category/overview";
	}

	@RequestMapping(value = "/manage", method = RequestMethod.GET)
	public ModelAndView mange(Locale locale, Model model) {

		ModelAndView modelAndView = new ModelAndView("course/manage");
		modelAndView
				.addObject("title", messageSource.getMessage(
						"course.manage.wrapper", null, locale));

		List<Course> list = courseDao.findAllCourses();

		modelAndView.addObject("courses", list);
		if (list == null) {
			throw new ResourceNotFoundException();
		}

		return modelAndView;
	}

	@RequestMapping(value = "chapter/manage", method = RequestMethod.GET)
	public ModelAndView chapter(Locale locale, Model model) {

		ModelAndView modelAndView = new ModelAndView("course/managechapter");
		modelAndView.addObject("title", messageSource.getMessage(
				"chapter.manage.wrapper", null, locale));

		List<Chapter> list = chapterDao.findAllChapters();

		modelAndView.addObject("chapters", list);
		if (list == null) {
			throw new ResourceNotFoundException();
		}

		return modelAndView;
	}

	@RequestMapping(value = "/manage/{courseId}", method = RequestMethod.GET)
	public ModelAndView edit(@PathVariable("courseId") long courseId,
			Locale locale) {

		Course course = courseDao.findById(courseId);
		List<Category> category = categoryDao.findAllCategories();

		if (course == null) {
			throw new ResourceNotFoundException();
		}

		ModelAndView modelAndView = new ModelAndView("course/edit");
		modelAndView.addObject("course", course);
		modelAndView.addObject("category", category);

		return modelAndView;
	}

	@RequestMapping(value = "/manage/{courseId}", method = RequestMethod.POST)
	public String doEdit(
			@PathVariable("courseId") long courseId,

			@RequestParam("name") String name,
			@RequestParam("description") String description,
			@RequestParam("price") double price,
			@RequestParam(value = "disabled", defaultValue = "false") boolean disabled,
			@RequestParam("category") long categoryId) {

		Category category = categoryDao.findById(categoryId);

		Course newCourse = courseDao.findById(courseId);
		newCourse.setName(name);
		newCourse.setDescription(description);
		newCourse.setPrice(price);
		newCourse.setCategory(category);
		newCourse.setDisabled(disabled);

		if (newCourse.getCategory().getId() != categoryId) {
			category.getCourses().add(newCourse);
			categoryDao.save(category);
		}

		courseDao.save(newCourse);

		return "redirect:/course/manage/{courseId}";
	}

	@RequestMapping(value = "/manage/new", method = RequestMethod.GET)
	public ModelAndView add(Locale locale) {

		ModelAndView modelAndView = new ModelAndView("course/newcourse");
		List<Category> category = categoryDao.findAllCategories();
		modelAndView.addObject("category", category);

		return modelAndView;
	}

	@RequestMapping(value = "/manage/new", method = RequestMethod.POST)
	public String doNew(
			@RequestParam("name") String name,
			@RequestParam("description") String description,
			@RequestParam("price") double price,
			@RequestParam(value = "disabled", defaultValue = "false") boolean disabled,
			@RequestParam("category") long categoryId,
			@RequestParam("startdate") Date startdate,
			@RequestParam("enddate") Date enddate)

	{
		
		Category category = categoryDao.findById(categoryId);
		
		Course newCourse = new Course();
		CoursePeriod period = new CoursePeriod();
				
		
		newCourse.setName(name);
		newCourse.setDescription(description);
		newCourse.setPrice(price);
		newCourse.setCategory(category);
		newCourse.setDisabled(disabled);
		period.setCourse(newCourse);
		period.setStart(startdate);
		period.setEnd(enddate);
		
		
		
		category.getCourses().add(newCourse);

		courseDao.save(newCourse);

		categoryDao.save(category);
		coursePeriodDao.save(period);
		return "redirect:/course/manage";
	}

	@RequestMapping(value = "/chapter/new", method = RequestMethod.GET)
	public ModelAndView addChapter(Locale locale) {

		ModelAndView modelAndView = new ModelAndView("course/newchapter");
		List<Course> course = courseDao.findAllCourses();
		modelAndView.addObject("course", course);

		return modelAndView;
	}

	@RequestMapping(value = "/chapter/new", method = RequestMethod.POST)
	public String addChapter(@RequestParam("name") String name,
			@RequestParam("content") String content,
			@RequestParam(value = "demo", defaultValue = "false") boolean demo,
			@RequestParam("course") long courseId) {

		Course course = courseDao.findById(courseId);

		Chapter newChapter = new Chapter();

		newChapter.setTitle(name);
		newChapter.setContent(content);
		newChapter.setDemo(demo);
		newChapter.setCourse(course);

		chapterDao.save(newChapter);

		course.getChapters().add(newChapter);

		courseDao.save(course);

		return "redirect:/course/chapter/manage";

	}

	@RequestMapping(value = "/chapter/edit/{chapterId}", method = RequestMethod.GET)
	public ModelAndView editChapter(@PathVariable("chapterId") long chapterId,
			Locale locale) {

		Chapter chapter = chapterDao.findById(chapterId);
		List<Course> course = courseDao.findAllCourses();

		if (course == null) {
			throw new ResourceNotFoundException();
		}

		ModelAndView modelAndView = new ModelAndView("course/editchapter");
		modelAndView.addObject("course", course);
		modelAndView.addObject("chapter", chapter);

		return modelAndView;
	}

	@RequestMapping(value = "/chapter/edit/{chapterId}", method = RequestMethod.POST)
	public String editChapter(@PathVariable("chapterId") long chapterId,

	@RequestParam("name") String name, @RequestParam("content") String content,
			@RequestParam(value = "demo", defaultValue = "false") boolean demo,
			@RequestParam("course") long courseId) {

		Course course = courseDao.findById(courseId);

		Chapter newChapter = new Chapter();

		newChapter.setId(chapterId);
		newChapter.setTitle(name);
		newChapter.setContent(content);
		newChapter.setDemo(demo);
		newChapter.setCourse(course);

		if (newChapter.getCourse().getId() != courseId) {
			course.getChapters().add(newChapter);
			courseDao.save(course);
		}
		chapterDao.save(newChapter);

		return "redirect:/course/chapter/manage";
	}

	@RequestMapping("/{courseId}/forum")
	public ModelAndView forum(@PathVariable("courseId") long courseId,
			Locale locale) {
		Course course = courseDao.findById(courseId);
		if (course == null) {
			throw new ResourceNotFoundException();
		}
		ModelAndView modelAndView = new ModelAndView("forum/forum");
		modelAndView.addObject("isLoggedIn", isUserLoggedIn());
		List<Topic> list = course.getTopics();
		modelAndView.addObject("topics", list);
		modelAndView.addObject("title",
				messageSource.getMessage("forum.viewing", null, locale));
		modelAndView.addObject("course", course);
		return modelAndView;
	}

	@RequestMapping("/{courseId}/forum/new_topic")
	public ModelAndView newTopic(@PathVariable("courseId") long courseId) {
		ModelAndView modelAndView = new ModelAndView("forum/new_topic");
		Course course = courseDao.findById(courseId);
		modelAndView.addObject("course", course);
		return modelAndView;
	}

	@RequestMapping("/{courseId}/forum/add_topic")
	public String addTopic(UserAuthentication userAuthentication,
			@PathVariable("courseId") long courseId,
			@RequestParam("TopicTitle") String topicTitle,
			@RequestParam("TopicText") String topicText) {
		Course temp = courseDao.findById(courseId);
		Topic topicToBeAdded = new Topic(temp, topicTitle,
				userAuthentication.getUser());
		topicDao.save(topicToBeAdded);
		Post postToBeAdded = new Post(topicText, userAuthentication.getUser(),
				topicToBeAdded);
		topicToBeAdded.addPost(postToBeAdded);
		postDao.save(postToBeAdded);
		topicDao.save(topicToBeAdded);
		temp.addTopic(topicToBeAdded);
		courseDao.save(temp);
		return "redirect:/course/" + courseId + "/forum";
	}

	@RequestMapping("/{courseId}/forum/{topicId}")
	public ModelAndView topic(UserAuthentication userAuthentication,
			@PathVariable("courseId") long courseId,
			@PathVariable("topicId") long topicId, Locale locale) {
		Course course = courseDao.findById(courseId);
		if (course == null) {
			throw new ResourceNotFoundException();
		}
		ModelAndView modelAndView = new ModelAndView("forum/topic");
		modelAndView.addObject("user", userAuthentication.getUser());
		Topic topic = course.getTopicById(topicId);
		modelAndView.addObject("topic", topic);
		List<Post> list = topic.getPosts();
		modelAndView.addObject("posts", list);
		modelAndView.addObject("course", course);
		modelAndView.addObject("title",
				messageSource.getMessage("forum.topic.viewing", null, locale));
		return modelAndView;
	}

	@RequestMapping("/{courseId}/forum/{topicId}/new_post")
	public ModelAndView newPost(@PathVariable("courseId") long courseId,
			@PathVariable("topicId") long topicId, Locale locale) {
		Course course = courseDao.findById(courseId);
		if (course == null) {
			throw new ResourceNotFoundException();
		}
		ModelAndView modelAndView = new ModelAndView("forum/new_post");
		Topic topic = course.getTopicById(topicId);
		modelAndView.addObject("topic", topic);
		modelAndView.addObject("title",
				messageSource.getMessage("forum.topic.viewing", null, locale));
		List<Post> list = topic.getPosts();
		Collections.reverse(list);
		modelAndView.addObject("posts", list);
		return modelAndView;
	}

	@RequestMapping("/{courseId}/forum/{topicId}/add_post")
	public String addPost(UserAuthentication userAuthentication,
			@PathVariable("courseId") long courseId,
			@PathVariable("topicId") long topicId,
			@RequestParam("PostText") String topicText) {
		Topic tempTopic = topicDao.findById(topicId);
		User user = userAuthentication.getUser();
		Post postToBeAdded = new Post(topicText, user, tempTopic);
		postDao.save(postToBeAdded);
		tempTopic.addPost(postToBeAdded);
		topicDao.save(tempTopic);
		return "redirect:/course/" + courseId + "/forum/" + topicId;
	}

	@RequestMapping("/{courseId}/forum/{topicId}/{postId}/edit")
	public ModelAndView editPost(UserAuthentication userAuthentication,
			@PathVariable("courseId") long courseId,
			@PathVariable("topicId") long topicId,
			@PathVariable("postId") long postId, Locale locale) {
		ModelAndView modelAndView = new ModelAndView("forum/edit_post");
		Course course = courseDao.findById(courseId);
		if (course == null) {
			throw new ResourceNotFoundException();
		}
		Topic topic = course.getTopicById(topicId);
		if (topic == null) {
			throw new ResourceNotFoundException();
		}
		Post post = topic.getPostById(postId);
		if (post == null) {
			throw new ResourceNotFoundException();
		}
		if (userAuthentication.getUser().getId() != post.getOwner().getId()) {
			throw new ForbiddenException();
		}
		modelAndView.addObject("title",
				messageSource.getMessage("forum.topic.viewing", null, locale));
		modelAndView.addObject("topic", topic);
		modelAndView.addObject("editedPost", post);
		List<Post> list = topic.getPosts();
		Collections.reverse(list);
		modelAndView.addObject("posts", list);
		return modelAndView;
	}

	@RequestMapping("/{courseId}/forum/{topicId}/{postId}/edit_post")
	public String doEditPost(UserAuthentication userAuthentication,
			@PathVariable("courseId") long courseId,
			@PathVariable("topicId") long topicId,
			@PathVariable("postId") long postId,
			@RequestParam("PostText") String postText) {
		Post post = postDao.findById(postId);
		if (post == null) {
			throw new ResourceNotFoundException();
		}
		if (userAuthentication.getUser().getId() != post.getOwner().getId()) {
			throw new ForbiddenException();
		}
		post.setText(postText);
		postDao.save(post);
		return "redirect:/course/" + courseId + "/forum/" + topicId;
	}

	@RequestMapping("/{courseId}/forum/{topicId}/edit")
	public ModelAndView editTopic(UserAuthentication userAuthentication,
			@PathVariable("courseId") long courseId,
			@PathVariable("topicId") long topicId, Locale locale) {
		Course course = courseDao.findById(courseId);
		if (course == null) {
			throw new ResourceNotFoundException();
		}
		ModelAndView modelAndView = new ModelAndView("forum/edit_topic");
		Topic topic = course.getTopicById(topicId);
		User user = userAuthentication.getUser();
		if (topic.getOwner().getId() != user.getId()) {
			throw new ForbiddenException();
		}
		modelAndView.addObject("title",
				messageSource.getMessage("forum.viewing", null, locale));
		modelAndView.addObject("user", user);
		modelAndView.addObject("course", course);
		modelAndView.addObject("topic", topic);
		modelAndView.addObject("post", topic.getFirstPost());
		return modelAndView;
	}

	@RequestMapping("/{courseId}/forum/{topicId}/edit_topic")
	public String doEditTopic(UserAuthentication userAuthentication,
			@PathVariable("courseId") long courseId,
			@PathVariable("topicId") long topicId,
			@RequestParam("TopicTitle") String topicTitle,
			@RequestParam("TopicText") String topicText) {
		Topic topic = topicDao.findById(topicId);
		if (topic == null) {
			throw new ResourceNotFoundException();
		}
		Post post = topic.getFirstPost();
		User user = userAuthentication.getUser();
		if (topic.getOwner().getId() != user.getId()) {
			throw new ForbiddenException();
		}
		topic.setTitle(topicTitle);
		topicDao.save(topic);
		post.setText(topicText);
		postDao.save(post);
		return "redirect:/course/" + courseId + "/forum/";
	}

	@RequestMapping(value = "assignment/upload/{courseId}/{chapterId}", method = RequestMethod.GET)
	public ModelAndView getUploadAssigmentForm(
			@PathVariable("courseId") long courseId,
			@PathVariable("chapterId") long chapterId,
			@RequestParam(value = "success", defaultValue = "false") boolean didFileUpload) {

		ModelAndView modelAndView = new ModelAndView("course/assignment/upload");
		modelAndView.addObject("courseId", courseId);
		modelAndView.addObject("chapterId", chapterId);
		modelAndView.addObject("assignmentFileItem", new AssignmentFileItem());
		modelAndView.addObject("didFileUpload", didFileUpload);

		return modelAndView;
	}

	@RequestMapping(value = "assignment/upload/{courseId}/{chapterId}", method = RequestMethod.POST)
	public String uploadAssigment(@PathVariable("courseId") long courseId,
			@PathVariable("chapterId") long chapterId,
			AssignmentFileItem assignment, BindingResult result,
			HttpServletRequest request) {

		final String UPLOAD_DIR_NAME = "uploads";

		// /WEB-INF/UPLOAD_DIR_NAME/{courseId}/{chapterId}
		File uploadDir = new File(request.getServletContext().getRealPath(
				"/WEB-INF")
				+ File.separator
				+ UPLOAD_DIR_NAME
				+ File.separator
				+ courseId
				+ File.separator + chapterId);

		if (!uploadDir.exists())
			uploadDir.mkdirs();

		CommonsMultipartFile file = assignment.getFile();
		String fileName = file.getOriginalFilename();

		// Try to get the extension of the file
		int lastPointIndex = fileName.lastIndexOf(".");

		String extension;

		// Invalid file, no extension
		if (lastPointIndex == -1 || lastPointIndex == fileName.length()) {
			logger.error("File has no extension, saving without extension");
			extension = "";
		} else {
			extension = fileName.substring(lastPointIndex + 1);
		}

		// Assume last part is the extension and create the complete
		// file name without any paths in front
		fileName = assignment.getName() + "." + extension;

		// Now create the file WITH the path
		File dest = new File(uploadDir.getAbsolutePath() + File.separator
				+ fileName);

		try {
			file.transferTo(dest);
		} catch (IllegalStateException e) {
			logger.error("Error while trying to upload the file ({})", e);
		} catch (IOException e) {
			logger.error("I/O error while trying to upload the file ({})", e);
		}

		return "redirect:/course/assignment/upload/" + courseId + "/"
				+ chapterId + "?success=true";
	}
}
